package org.su.mamamiya.swagger.processor;

import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeTranslator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import org.su.mamamiya.core.ast.*;
import org.su.mamamiya.swagger.annotation.SwaggerMethod;
import org.su.mamamiya.swagger.annotation.SwaggerTemplate;
import org.su.mamamiya.swagger.context.SwaggerContext;
import org.su.mamamiya.swagger.utils.SwaggerDocHelper;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeKind;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


/**
 * Created by yangchuan on 2019-10-02 15:37
 */
@SupportedAnnotationTypes({"org.springframework.web.bind.annotation.RestController",
        "org.springframework.stereotype.Controller",
        "org.su.duck.swagger.SwaggerTemplate"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ApiProccessor extends AbstractProcessor {

    private JavacTrees trees;
    private SwaggerContext swaggerContext;
    private AstProccessor astProccessor ;
    private DocHelper docHelper ;
    private Logger logger;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.trees = JavacTrees.instance(processingEnv);
        swaggerContext = new SwaggerContext();

        astProccessor = new AstProccessor(processingEnv);
        docHelper = new DocHelper(astProccessor);
        logger = new Logger(processingEnv.getMessager());
    }


    private void initSwaggerContext(RoundEnvironment roundEnv){
        for (Element element : roundEnv.getElementsAnnotatedWith(SwaggerTemplate.class)) {
            for (Element item : element.getEnclosedElements()) {
                if (item.getKind() == ElementKind.METHOD && item.getModifiers().contains(Modifier.PUBLIC)) {
                    ExecutableElement amethod = (ExecutableElement) item;
                    JCTree.JCMethodDecl methodDecl = trees.getTree(amethod);
                    for (JCTree.JCAnnotation annotation : methodDecl.mods.annotations) {
                        String annotationType = annotation.getAnnotationType().toString();
                        if (annotationType.contains("ApiImplicitParams")) {
                            JCTree.JCAssign assign = (JCTree.JCAssign) annotation.args.get(0);
                            swaggerContext.setApiImplicitParams((JCTree.JCNewArray)assign.rhs);
                        }

                        if (annotationType.contains("ApiResponses") ) {
                            JCTree.JCAssign assign = (JCTree.JCAssign) annotation.args.get(0);
                            swaggerContext.setApiResponse((JCTree.JCNewArray)assign.rhs);
                        }
                        if(swaggerContext.isNotNull()){
                            return;
                        }

                    }
                }
            }
        }
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        JCTree t = null;
        initSwaggerContext(roundEnv);
        Set<? extends Element> restControllers = roundEnv.getElementsAnnotatedWith(RestController.class);
        process(t,roundEnv,restControllers);
        Set<? extends Element> controllers = roundEnv.getElementsAnnotatedWith(Controller.class);
        process(t,roundEnv,controllers);


        return true;
    }


    private void process(JCTree t, RoundEnvironment roundEnv,Set<? extends Element> controllers){
        for (Element element : controllers) {
            if (element.getKind() != ElementKind.CLASS) {
                logger.error(String.format("Only class can be annotated with %s", "@RestController or @Controller"));
                return;
            }

            if(swaggerContext.isNotResponsesNull()){
                astProccessor.addImportInfo(element,"io.swagger.annotations","ApiResponse");
            }
            t = trees.getTree(element);

            t.accept(new TreeTranslator() {

                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    String classDoc = docHelper.getClassDoc(element);// elementUtils.getDocComment(element);
                    if(StringUtils.isBlank(classDoc)){
                        logger.error(String.format("请给%s类添加类注释",jcClassDecl.name.toString()),element);
                        return;
                    }

                    SwaggerDocHelper.setApiTitle(astProccessor, jcClassDecl, classDoc,swaggerContext);

                    for (Element item : element.getEnclosedElements()) {
                        if (item.getKind() == ElementKind.METHOD && item.getModifiers().contains(Modifier.PUBLIC)) {
                            ExecutableElement amethod = (ExecutableElement) item;
                            JCTree.JCMethodDecl methodDecl = trees.getTree(amethod);
                            String arg = null;
                            Map<String, Param> params = new HashMap<>();
                            Param param = null;
                            for (VariableElement ve : amethod.getParameters()) {
                                arg = ve.getSimpleName().toString();//"arg" + ++i;
                                param = new Param();
                                param.setRequired(true);
                                param.setName(arg);
                                setPramaDataType(ve, arg, param);
                                setDefaultPramater(ve, arg, param);
                                params.put(arg, param);
                                //JCTree.JCVariableDecl jcVariableDecl = trees.getTree(ve);
                                //SwaggerDocHelper.setParamApi(names, treeMaker, methodDecl, doc, params, item, swaggerContext, messager);
                            }
                            if (item.getAnnotation(SwaggerMethod.class) != null || swaggerContext.isNotParamsNull()) {
                                String doc = getDoc(item);
                                if (doc.length() == 0) {
                                    logger.error(String.format("请给%s的%s方法写好注释", jcClassDecl.name.toString(), amethod.getSimpleName().toString()),item);
                                }
                                SwaggerDocHelper.getApi(astProccessor, methodDecl, doc, params, item, swaggerContext, logger);
                            }


                        }
                    }
                    super.visitClassDef(jcClassDecl);
                }
            });


        }
    }

    private boolean isCollections(String str){
        return str.startsWith("java.util.List") ||
                str.startsWith("java.util.ArrayList") ||
                str.startsWith("java.util.LinkedList") ||
                str.startsWith("java.util.Set") ||
                str.startsWith("java.util.HashSet") ||
                str.startsWith("java.util.TreeSet") ||
                str.startsWith("java.util.SortedSet")||
                str.startsWith("java.util.Vector");
    }

    public void setPramaDataType(VariableElement ve, String name, Param param) {
        String str = ve.asType().toString();
        param.setAllowMultiple(ve.asType().getKind().equals(TypeKind.ARRAY)
                    || isCollections(str));
        if (ve.asType().getKind().isPrimitive()) {
            param.setDataType(ve.asType().getKind().name());
        } else {
            //messager.printMessage(Diagnostic.Kind.WARNING,str);
            String temp = str.substring(str.lastIndexOf(".") + 1).toLowerCase();
            param.setDataType(DataType.getDataType(temp));
        }
        String s = ve.asType().toString();
        if (s.indexOf("/definitions/") > -1) {
            s = s.substring("/definitions/".length());
        }
        try {
            param.setDataTypeClass(ve.asType());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean setDefaultPramater(VariableElement ve, String name, Param param) {
        if (ve.getAnnotation(RequestParam.class) != null) {
            param.setParamType("query");
            param.setRequired(ve.getAnnotation(RequestParam.class).required());
            return false;
        }
        if (ve.getAnnotation(RequestBody.class) != null) {
            param.setParamType("body");
            param.setRequired(ve.getAnnotation(RequestBody.class).required());
            return false;
        }
        if (ve.getAnnotation(PathVariable.class) != null) {
            param.setParamType("path");
            param.setRequired(ve.getAnnotation(PathVariable.class).required());
            return false;
        }
        if (ve.getAnnotation(RequestHeader.class) != null) {
            param.setParamType("header");
            param.setRequired(ve.getAnnotation(RequestHeader.class).required());
            return false;
        }

        if (name.endsWith("Body")) {
            param.setParamType("body");
        } else {
            if (ve.asType().getKind().isPrimitive()) {
                param.setParamType("query");
            } else {
                switch (param.getDataType()) {
                    case "boolean":
                    case "char":
                    case "byte":
                    case "short":
                    case "int":
                    case "float":
                    case "double":
                    case "long":
                    case "String":
                    case "string":
                    case "date":
                    case "biginteger":
                    case "bigdecimal":
                        param.setParamType("query");
                        break;
                    default:
                        param.setParamType("form");
                        break;
                }
            }
        }
        return true;
    }

    private String getDoc(Element element) {
        try {
            String doc = astProccessor.getDoc(element);
            if (StringUtils.isBlank(doc)) {
                return "";
            }
            return doc;
        } catch (Exception e) {
            return "";
        }
    }




}
